Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ShellParams protocol #772

Merged
merged 3 commits into from
Sep 11, 2023

Conversation

JohnAZoidberg
Copy link
Contributor

@JohnAZoidberg JohnAZoidberg commented Apr 28, 2023

Useful to get the shell arguments for a commandline application.

Running example application on UEFI shell:

Shell> fs0:EFI\Boot\BootX64.efi
Args: ["FS0:\\EFI\Boot\BootX64.efi"]
Num args: 1
Shell> fs0:EFI\Boot\BootX64.efi foo bar
Args: ["FS0:\\EFI\Boot\BootX64.efi", "foo", "bar"]
Num args: 3
First real arg: 'foo'
Shell> 

Checklist

  • Sensible git history (for example, squash "typo" or "fix" commits). See the Rewriting History guide for help.
  • Update the changelog (if necessary)

@JohnAZoidberg
Copy link
Contributor Author

Hmm, either need to have the test runner not run this test. Or need to run it from a shell.

@nicholasbishop
Copy link
Member

Hmm, either need to have the test runner not run this test. Or need to run it from a shell.

The question of how to run tests that rely on the shell came up in #596 too (cc @timrobertsdev). I haven't looked into it myself and don't have a good answer for now.

I think what you've done in this PR of putting it in a separate file under examples/ might be a good compromise for now though; it will get compiled, but not run automatically by anything, so it's OK that we don't have an env set up for it.

@@ -0,0 +1,60 @@
// ANCHOR: all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These ANCHOR comments are just used by mdbook (see https://rust-osdev.github.io/uefi-rs/HEAD/how_to/protocols.html#walkthrough for example), so unless you are looking to add this example to the book, you can leave these out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to have a shell application example in the book.
However it would be more useful once the shell protocol is merged.

/// Get a Vec of the shell parameter arguments
#[cfg(feature = "alloc")]
#[must_use]
pub fn get_args(&self) -> Vec<String> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than collecting into a Vec here, could we return an iterator? That would allow it to work without alloc, and if the user actually needs a Vec they can call .collect() on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's a good idea!

Do you think it would be useful to add something like this as well?

    /// Get a slice of the args, as Char16 pointers
    pub fn get_args_slice(&self) -> &[*const Char16] {
        unsafe { from_raw_parts(self.argv, self.argc) }
    }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest not exposing that as pub fn unless we have a specific real-world need for the API. Most of the uefi-rs API tries to provide safe wrappers rather than exposing raw pointers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about returning a slice or an iterator to CStr16?
I couldn't figure out how to return a &[CStr16], not sure if that's possible.
But an Iterator<Item = CStr16> would be doable. Or do you generally just want to return utf8 Strings only?

Copy link
Contributor

@phip1611 phip1611 May 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't figure out how to return a &[CStr16], not sure if that's possible.

Does this help?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm but can I turn a &[*const Char16] into a &[CStr16]?
Not without copying, right?

@nicholasbishop
Copy link
Member

Hmm, either need to have the test runner not run this test. Or need to run it from a shell.

We've landed a change to the test-runner so that it runs under the UEFI shell. So you should be able to add runtime tests now :)

@JohnAZoidberg
Copy link
Contributor Author

Awesome! I'll try that, thanks!

@JohnAZoidberg
Copy link
Contributor Author

We've landed a change to the test-runner so that it runs under the UEFI shell. So you should be able to add runtime tests now :)

Added! :D

As I understand, currently all tests are run by a single executable, test_runner.efi.
It's called by the shell, so I just tested for those shell parameters.

For more testes I created an example PR: #922. Not sure if you like how I run the other tests.

@JohnAZoidberg JohnAZoidberg force-pushed the shell-params branch 2 times, most recently from 1c4e7b6 to 2247e23 Compare August 31, 2023 05:58
pub fn get_args(&self) -> impl Iterator<Item = String> {
unsafe {
from_raw_parts(
self.0.argv.cast::<*const data_types::chars::Char16>(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why but the compiler thinks argv is *const *const u16 instead of *const *const Char16.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type of Char16 is defined differently in uefi-raw vs uefi. uefi-raw just uses a u16 so any value is accepted, whereas in uefi we use the ucs2 crate that places some restrictions on the valid character values. (At some point I'd like to improve this situation, see #809 (comment) for more background.)

All of which is to say, this is expected :)

#[derive(Debug)]
#[repr(C)]
pub struct ShellParametersProtocol {
/// Pointer to a list of arguments
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: period at ends of comments.

pub fn get_args(&self) -> impl Iterator<Item = String> {
unsafe {
from_raw_parts(
self.0.argv.cast::<*const data_types::chars::Char16>(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type of Char16 is defined differently in uefi-raw vs uefi. uefi-raw just uses a u16 so any value is accepted, whereas in uefi we use the ucs2 crate that places some restrictions on the valid character values. (At some point I'd like to improve this situation, see #809 (comment) for more background.)

All of which is to say, this is expected :)

impl ShellParameters {
/// Get an iterator of the shell parameter arguments
#[cfg(feature = "alloc")]
pub fn get_args(&self) -> impl Iterator<Item = String> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


impl ShellParameters {
/// Get an iterator of the shell parameter arguments
#[cfg(feature = "alloc")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of our APIs return CStr16s rather than converting to String. (Since there's a good chance that in real usage you'll pass these strings back into other uefi-rs methods, it makes sense to avoid the round-trip conversion from UCS-2 to UTF-8 and back). So this can change to:

pub fn args(&self) -> impl Iterator<Item = &CStr16> {
    self.get_args_slice()
        .iter()
        .map(|x| unsafe { CStr16::from_ptr(*x) })
}

And doesn't need to be gated behind alloc then.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok sure, that's a good idea.


/// Get a slice of the args, as Char16 pointers
#[must_use]
pub fn get_args_slice(&self) -> &[*const Char16] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along with changing the other method to be available without alloc, I think we can drop the pub from this method and keep it as an internal helper.

for arg in shell_params.get_args_slice() {
let arg_str = unsafe { CStr16::from_ptr(*arg) };
info!(" '{}'", arg_str);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Since the result of getting the args is fully checked below, let's drop printing the args (many of our tests do print stuff, but it's only really necessary in cases where the output can ever change).

Useful to get the shell arguments for a commandline application.

Signed-off-by: Daniel Schaefer <[email protected]>
Signed-off-by: Daniel Schaefer <[email protected]>
@nicholasbishop
Copy link
Member

Lgtm, thanks!

@nicholasbishop nicholasbishop added this pull request to the merge queue Sep 11, 2023
Merged via the queue into rust-osdev:main with commit eb33785 Sep 11, 2023
12 checks passed
@JohnAZoidberg JohnAZoidberg deleted the shell-params branch June 25, 2024 01:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants